/*
 * @(#)DynamicClassLoader.java 1.0 2008/12/06
 */
package stardust.core.java;


import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.ZipException;
import stardust.core.io.IOUtils;
import stardust.core.java.reflect.MethodReflect;
import stardust.core.log.Log;


/**
 * <code>DynamicClassLoader</code> 类提供在文件系统、独立的JAR文件里动态加载 <tt>Class</tt> 的能力。
 * <p>
 * 支持 ClassPath 目录，JAR 文件中的类定义。
 * <p/>
 * @see ClassLoader
 * @version 1.0, 2009/12/01
 * @author ChenChang
 */
public class DynamicClassLoader extends ClassLoader
{

  public static final int BUFFER_SIZE = 1024;

  private Log log = new Log(DynamicClassLoader.class);

  private Collection<File> jar = new HashSet<File>();

  private Collection<File> classpath = new HashSet<File>();

  private boolean defineClassInSystem;

  public DynamicClassLoader()
  {
    this(Thread.currentThread().getContextClassLoader());
  }

  public DynamicClassLoader(ClassLoader parent)
  {
    super(parent);
  }

  public DynamicClassLoader(boolean defineClassInSystem)
  {
    this.defineClassInSystem = defineClassInSystem;
  }

  public boolean isDefineClassInSystem()
  {
    return this.defineClassInSystem;
  }

  public boolean addJar(File f)
  {
    if (f != null)
    {
      return this.jar.add(f);
    }

    return false;
  }

  public boolean addClasspath(File f)
  {
    if (f != null)
    {
      return this.classpath.add(f);
    }

    return false;
  }

  /**
   * 转换全限定类名为文件系统路径。 <tt>formatClassName("java.lang.Object")</tt>转换为
   * <tt>"java\lang\Object"</tt>
   * <p/>
   * @param className
   * 源全限定类名。
   * <p/>
   * @return 转换后的名称。
   */
  private String formatName(String className)
  {
    return className.replaceAll("\\.", IOUtils.FILE_SEPARATOR.equals("\\") ? "\\\\" : IOUtils.FILE_SEPARATOR);
  }

  /**
   * 从 <tt>ClassLoader</tt> 上下文配置中搜寻指定的资源，返回资源的 <tt>InputStream</tt> 对象。
   * <p/>
   * @param name
   * 源相对路径。
   * <p/>
   * @return 源所对应的 <tt>InputStream</tt> 对象。
   */
  private InputStream getResourceAsInputStream(String name) throws ClassNotFoundException
  {
    File parent;
    File resource;
    Iterator<File> iterator;

    // 在Classpath里搜寻资源
    iterator = this.classpath.iterator();
    while (iterator.hasNext())
    {
      parent = iterator.next();
      resource = new File(parent, name);

      if (resource.exists())
      {
        try
        {
          this.log.trace("加载资源：", resource.getAbsolutePath());

          return new FileInputStream(resource);
        }
        catch(FileNotFoundException e)
        {
          throw new ClassNotFoundException("文件不存在或拒绝访问，无法打开输入流：" + resource.getPath() + "。", e);
        }
      }
    }

    // 在Jar里搜寻资源
    Collection<File> jars = new HashSet<File>();
    JarFile jarFile;
    JarEntry jarEntry;

    jars.addAll(this.jar);

    iterator = jars.iterator();

    while (iterator.hasNext())
    {
      parent = (File) iterator.next();

      try
      {
        jarFile = new JarFile(parent, true, JarFile.OPEN_READ);
        jarEntry = jarFile.getJarEntry(name);

        if (jarEntry != null)
        {
          this.log.trace("加载资源：[", parent.getAbsolutePath(), "]", name);

          return jarFile.getInputStream(jarEntry);
        }
      }
      catch(ZipException e)
      {
        throw new ClassNotFoundException("无法打开 JAR 文件，文件内容格式可能已损坏：" + parent.getPath() + "。", e);
      }
      catch(IOException e)
      {
        throw new ClassNotFoundException("文件不存在或拒绝访问，无法打开输入流：" + parent.getPath() + "。", e);
      }
    }

    this.log.warning("无法加载资源：", name);

    return null;
  }

  /**
   * 通过类全限定名称和字节码输入流定义一个类。
   * <p/>
   * @param name
   * 期望的类全限定名称或 <tt>null</tt> 值。
   * @param input
   * 类的字节码输入流。
   * @param closeStream
   * 为 <tt>true</tt> 值时在读取字节码结束后关闭输入流。
   * <p/>
   * @return 由字节码产生的 <tt>Class</tt> 对象。
   * <p/>
   * @throws ClassNotFoundException
   * 读取字节码时产生 IO 失败。
   */
  public Class<?> defineClass(String name, InputStream input, boolean closeStream) throws ClassNotFoundException
  {
    byte[] buffer = new byte[BUFFER_SIZE];
    int len;

    try
    {
      // 尝试从缓存获取Class对象。
      try
      {
        return Class.forName(name);
      }
      catch(ClassNotFoundException ex)
      {
        ;
      }

      // 从输入流读取字节码存储到数组
      ByteArrayOutputStream outBuffer = new ByteArrayOutputStream(BUFFER_SIZE);

      while ((len = input.read(buffer)) != -1)
      {
        outBuffer.write(buffer, 0, len);
      }
      buffer = outBuffer.toByteArray();

      this.log.trace("加载类：", name);

      return super.defineClass(name, buffer, 0, buffer.length);
    }
    catch(IOException e)
    {
      throw new ClassNotFoundException("无法为类装载字节数据：" + name, e);
    }
    finally
    {
      // 如果closeStream标志为true关闭数据输入流
      if (closeStream)
      {
        try
        {
          input.close();
        }
        catch(IOException e)
        {
          throw new ClassNotFoundException("无法载入类：" + name, e);
        }
      }
    }
  }

  /**
   * 通过 <tt>ClassLoader</tt> 载体 、全限定类名和字节码定义一个 <tt>Class</tt> 对象。
   * <p/>
   * @param classLoader
   * 定义类的 <tt>ClassLoader</tt> 载体。
   * @param className
   * 全限定类名。
   * @param byteCode
   * 类的字节码。
   * <p/>
   * @return 成功返回定义的类。
   */
  public static Class<?> defineClass(ClassLoader classLoader, String className, byte[] byteCode)
  {
    Class<?>[] parameterTypes =
    {
      String.class, byte[].class, int.class, int.class
    };
    Method defineClass = MethodReflect.findMethod(ClassLoader.class, "defineClass", parameterTypes);

    try
    {
      return (Class<?>) (MethodReflect.invoke(classLoader, defineClass, className, byteCode, 0, byteCode.length));
    }
    catch(Exception e)
    {
      throw new SystemError(e);
    }
  }

  /**
   * 通过类全限定名称搜寻一个类。
   * <p/>
   * @param name
   * 期望搜寻的类全限定名称。
   * <p/>
   * @return 载入的 <tt>Class</tt> 对象。
   * <p/>
   * @throws ClassNotFoundException
   * 如果读取字节码时产生 IO 失败或类未找到。
   */
  protected Class<?> findClass(String name) throws ClassNotFoundException
  {
    InputStream findClass = this.getResourceAsInputStream(formatName(name) + ".class");

    if (findClass == null)
    {
      throw new ClassNotFoundException("ClassLoader在上下文中无法找到类:" + name + "。");
    }
    else
    {
      return this.defineClass(name, findClass, true);
    }
  }

  /**
   * 通过名称获取资源的输入流。
   * <p/>
   * @param name
   * 资源的路径。
   * <p/>
   * @return 资源的输入流。
   */
  public InputStream getResourceAsStream(String name)
  {
    InputStream stream;

    try
    {
      stream = super.getResourceAsStream(name);

      if (stream == null)
      {
        stream = this.getResourceAsInputStream(name);
      }

      return stream;
    }
    catch(ClassNotFoundException e)
    {
      return null;
    }
  }

  /**
   * 通过名称查找动态链接库。
   * <p/>
   * @param libname
   * 动态链接库的路径
   * <p/>
   * @return 找到的动态链接库路径
   */
  protected String findLibrary(String libname)
  {
    return super.findLibrary(libname);
  }

}
